home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / admin / numexec < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  45.8 KB  |  1,288 lines

  1. #!/usr/local/bin/gawk -f
  2. # @(#) numexec.gawk 2.0 96/05/13
  3. # 92/09/09 john h. dubois iii (john@armory.com)
  4. # 94/02/12 Added -h option.
  5. # 96/05/13 2.0 Converted to pure gawk program.  Added all options other than h.
  6.  
  7. BEGIN {
  8.     Name = "numexec"
  9.     Usage = Name " [-phorRPaGx] [acctcom-options]"
  10.     ARGC = Opts(Name,Usage,"hpoxw<U<crRPaG",0,"","",0,"",1)
  11.  
  12.     if ((Err = ExclusiveOptions("w,U;r,R,P,a,G",Options)) != "") {
  13.     printf "Error: %s\n",Err > "/dev/stderr"
  14.     Err = 1
  15.     exit(1)
  16.     }
  17.     progDiv = 100
  18.     if ("h" in Options) {
  19.     printf \
  20. "%s: Accumulate statistics for each unique process name recorded in the\n"\
  21. "process accounting logs, and list them in order of number of times\n"\
  22. "executed, from most to least.  For each process, the users who executed\n"\
  23. "that process the most often are also listed.\n"\
  24. "%s\n"\
  25. "The first option encountered that is not one of the options explicitly\n"\
  26. "mentioned below causes all remaining arguments to be passed as arguments\n"\
  27. "to acctcom.  These options as described in the acctcom(ADM) man page, and\n"\
  28. "can be used e.g. to select particular processes to be counted and to read\n"\
  29. "a pacct file other than the default (/usr/adm/pacct).  If an acctcom\n"\
  30. "option needs to be given but there is a %s option with the same flag\n"\
  31. "letter, use \"--\" to separate the %s options from the acctcom options.\n"\
  32. "Note: options that change the output format of acctcom may confuse %s and\n"\
  33. "cause incorrect output.\n"\
  34. "Options:\n"\
  35. "-h: Print this help.\n"\
  36. "-o: Process acctcom output instead of running acctcom to generate output.\n"\
  37. "    If this option is given, no acctcom-options should be given; instead,\n"\
  38. "    the names of one or more files of acctcom output should be given.  If\n"\
  39. "    no filenames are given, the standard input is read.\n"\
  40. "-p: Print progress characters (one for each %d records read) to standard\n"\
  41. "    error.  If it is possible to determine how many records are to be\n"\
  42. "    processed, numbers from 0 through 9 are printed, to indicate the\n"\
  43. "    fraction of records processed.  If not, dots are printed.\n"\
  44. "-c: After each user name, report the number of times that that user\n"\
  45. "    executed the process being listed.  No count is appended for instances\n"\
  46. "    in which the process was executed exactly once.  Fewer user reports\n"\
  47. "    will fit on the screen with this on.\n"\
  48. "-U<users>: Report on the top <users> users who executed each process.  -U\n"\
  49. "    is an alternative to the -w option; they should not be used together.\n"\
  50. "    Use -U0 to not include any user-specific information.\n"\
  51. "-w<width>: Set the display width.  This determines how many users are\n"\
  52. "    listed for each process.  <width>-1 columns of the display are used.\n"\
  53. "    The default display width is the number of columns that the terminal\n"\
  54. "    that %s is run on has, or 80 if that cannot be determined.  If -w0\n"\
  55. "    is given, all users will be reported on.  This may produce very long\n"\
  56. "    output lines.\n"\
  57. "-r: Sort by cumulative runtime (runtime used by all procs of one name).\n"\
  58. "-R: Sort by average runtime.\n"\
  59. "-P: Sort by cumulative CPU time (CPU time used by all procs of one name).\n"\
  60. "-a: Sort by average CPU time.\n"\
  61. "-G: Sort by hog factor (average CPU time / average runtime).\n"\
  62. "-x: Turn on debugging\n",Name,Usage,Name,Name,Name,progDiv,Name
  63.     # ^^^ must mention -x though it is not intended for general use, because
  64.     # we claim above that all options interpreted internally to this program
  65.     # are mentioned here.
  66.     exit 0
  67.     }
  68.  
  69.     Debug = "x" in Options
  70.     if (Progress = "p" in Options) {
  71.     progressChar = "."
  72.     recNum == progDiv
  73.     }
  74.     if ("o" in Options) {
  75.     if (ARGC > 1) {
  76.         tRecords = GetFilesizeSum(QuoteArgs(ARGV,1,ARGC-1))
  77.         if (tRecords) {
  78.         # Generally 72 chars per acctcom output record.
  79.         # Determine approx. how many records in 1/10 of the input.
  80.         if (Debug)
  81.             printf "Approximately %d records to process.\n",
  82.             tRecords > "/dev/stderr"
  83.         tRecords /= 720 * progDiv
  84.         progressChar = 0
  85.         }
  86.     }
  87.     while (getline == 1)
  88.         ProcLine()
  89.     }
  90.     else {
  91.     # gawk closes fd 0, and acctcom does not like that, so redirect it
  92.     Cmd = "acctcom </dev/null -v"
  93.     if (ARGC < 2)
  94.         paFiles = "/usr/adm/pacct"
  95.     else  {
  96.         Args = QuoteArgs(ARGV,1,ARGC-1)
  97.         if (ARGV[1] !~ /^-/)
  98.         paFiles = Args
  99.         Cmd = Cmd " " Args
  100.     }
  101.     if (paFiles) {
  102.         tRecords = GetFilesizeSum(paFiles)
  103.         if (Debug)
  104.         printf "Approximately %d records to process.\n",
  105.         tRecords > "/dev/stderr"
  106.         if (tRecords) {
  107.         # 32 chars per process accounting record.
  108.         # Determine approx. how many records in 1/10 of the input.
  109.         tRecords /= 320 * progDiv
  110.         progressChar = 0
  111.         }
  112.     }
  113.     if (Debug)
  114.         printf "Command is: %s\n",Cmd
  115.     while ((Cmd | getline) == 1)
  116.         ProcLine()
  117.     }
  118.     if (Progress)
  119.     print "" > "/dev/stderr"
  120.     if ("r" in Options)
  121.     Num = qsortArbIndByValue(Time,k)
  122.     else if ("P" in Options)
  123.     Num = qsortArbIndByValue(CPU,k)
  124.     else if ("R" in Options) {
  125.     for (cmd in Time)
  126.         indArr[cmd] = Time[cmd] / Count[cmd]
  127.     Num = qsortArbIndByValue(indArr,k)
  128.     }
  129.     else if ("a" in Options) {
  130.     for (cmd in Time)
  131.         indArr[cmd] = CPU[cmd] / Count[cmd]
  132.     Num = qsortArbIndByValue(indArr,k)
  133.     }
  134.     else if ("G" in Options) {
  135.     for (cmd in Time)
  136.         indArr[cmd] = CPU[cmd] / Time[cmd]
  137.     Num = qsortArbIndByValue(indArr,k)
  138.     }
  139.     else
  140.     Num = qsortArbIndByValue(Count,k)
  141.     Format = "%-8s %5s %5s %7s %5s %6s %5s"
  142.     Header = \
  143.     sprintf(Format,"Command","Count","Time","AvgTime","CPU","AvgCPU","%CPU")
  144.     UInd[1] = "a"    # convince awk that UInd is an array
  145.     Verbose = "c" in Options
  146.     if ("U" in Options)
  147.     nUsers = Options["U"]
  148.     else {
  149.     if ("w" in Options)
  150.         Width = Options["w"]
  151.     else {
  152.         # Use HeadTailInit() to set columns
  153.         HeadTailInit(-1,0)
  154.         Width = COLUMNS
  155.     }
  156.     nUsers = int((Width - length(Header) - 1)/(Verbose ? 14 : 9))
  157.     }
  158.     print Header (nUsers ? " Top users" : "")
  159.     AllVals = (Width != "" && !Width)    # -w0
  160.     for (i = Num; i; i--) {
  161.     Cmd = k[i]
  162.     Ct = Count[Cmd]
  163.     Tm = Time[Cmd]
  164.     C = CPU[Cmd]
  165.     printf Format, Cmd, i2met(Ct,5,0), i2emet(Tm,5,0,0,1),
  166.     i2emet(Tm/Ct,7,0,0,1), i2emet(C,5,0,0,1), i2emet(C/Ct,6,0,0,1),
  167.     sprintf("%4.1f",100 * C/Tm)
  168.     ClearArr(UserCt)
  169.     ClearArr(UInd)
  170.     for (User in Users)
  171.         if ((Cmd,User) in UserCount)
  172.         UserCt[User] = UserCount[Cmd,User]
  173.     if (AllVals)
  174.         NumUsers = qsortArbIndByValue(UserCt,UInd)
  175.     else
  176.         NumUsers = TopValues(UserCt,UInd,nUsers)
  177.     if (Debug)
  178.         printf "TopValues() returned %d items\n",NumUsers > "/dev/stderr"
  179.     for (UserNum = 1; UserNum <= NumUsers; UserNum++) {
  180.         User = UInd[AllVals ? NumUsers - UserNum + 1 : UserNum]
  181.         if (Verbose) {
  182.         uCount = UserCt[User]
  183.         printf " %-13s",User (uCount > 1 ? "=" i2met(uCount,4,0) : "")
  184.         }
  185.         else
  186.         printf " %-8s",User
  187.     }
  188.     print ""
  189.     }
  190. }
  191.  
  192. function GetFilesizeSum(Files,  Cmd,Size) {
  193.     Cmd = "ls 2>/dev/null -Ws -dog " Files
  194.     while ((Cmd | getline) == 1) {
  195.     Size += $3
  196.     if (Debug)
  197.         printf "Got size %d from ls line: %s\n",$3,$0 > "/dev/stderr"
  198.     }
  199.     close(Cmd)
  200.     return Size
  201. }
  202.  
  203. function ProcLine() {
  204.     # Column headings won't appear in normal output since we give acctcom -v,
  205.     # but are liable to be in input passwd with -o.
  206.     if (NF < 8 || $1 == "ACCOUNTING" || $1 == "NAME")
  207.     return
  208.     if (Progress && ++recNum >= progDiv) {
  209.     recNum = 0
  210.     printf "%s",progressChar > "/dev/stderr"
  211.     if (tRecords && ++charNum >= tRecords) {
  212.         progressChar++
  213.         charNum = 0
  214.     }
  215.     }
  216.     sub("^#","",$1)
  217.     Count[$1]++
  218.     UserCount[$1,$2]++
  219.     Users[$2]
  220.     Time[$1] += $6
  221.     CPU[$1] += $7
  222. }
  223. ### Start of library routines
  224.  
  225. # jhdiii 96/05/13
  226. # Returns in Top[1..Num] the indexes of the top Num values in Values[], with
  227. # Top[1] being the largest value and Top[Num] being the Num'th largest value.
  228. # The elements in Values[] are compared as numeric values.
  229. # If there are n values in Values[] where n is less than Num, Top[] will have
  230. # only indices 1..n.  Values[] is not touched.
  231. # TopValues() is intended as an efficient alternative to sorting when the
  232. # number of elements in Values[] is liable to be large and the number of top
  233. # elements needed is small and fixed.
  234. # Return value: the largest index created in Top[].
  235. function TopValues(Values,Top,Num,  i,num,topInd,topValue,GotInd) {
  236.     num = 0
  237.     for (i = 1; i <= Num; i++) {
  238.     topInd = ""
  239.     for (ind in Values)
  240.         if ((topInd == "" || Values[ind] > topValue) && !(ind in GotInd)) {
  241.         topInd = ind
  242.         topValue = Values[ind]
  243.         }
  244.     if (topInd == "")
  245.         break
  246.     Top[++num] = topInd
  247.     GotInd[topInd]
  248.     }
  249.     return num
  250. }
  251.  
  252. # jhdiii 96/05/14
  253. # Converts all of the elements of Arr with integer indices from Min to Max
  254. # to a string of arguments individually quoted so that when passed to the
  255. # shell as a command line to execute they will remain individual arguments and
  256. # will not undergo any type of substitution.  Equivalent to "$@" in sh.
  257. function QuoteArgs(Arr,Min,Max,  i,S,Arg) {
  258.     for (i = Min; i <= Max; i++) {
  259.     Arg = Arr[i]
  260.     # Args are quoted with single-quotes.  To deal with single-quotes in
  261.     # the arg, whereever one occurs, close the single-quotes, add a
  262.     # backslash-escaped single quote, then re-open the single quotes.
  263.     gsub("'","'\\''",Arg)
  264.     S = S " '" Arg "'"
  265.     }
  266.     return substr(S,2)    # Lose initial space
  267. }
  268.  
  269. function ClearArr(Arr,  Elem) {
  270.     for (Elem in Arr)
  271.     delete Arr[Elem]
  272. }
  273.  
  274. # @(#) i2met.awk 1.0 96/02/13
  275. # jhdiii 96/01/14
  276. # Convert positive integer value Value to a string at most MaxLen characters
  277. # long.  This is done by converting the integer to a string of the form n*m,
  278. # where m is a metric suffix from: K M G
  279. # If Pow2 is true, then each factor of 1K is taken to be 1024; if it is false,
  280. # it is taken to be 1000.
  281. # MaxLen must be between 4 and 9. 
  282. # Value may be any integer from 0..maxint
  283. # If Units is given, it is the units that Value is passed in, where
  284. # Units=1 means Value is in K; Units=2 means Value is in M, etc.
  285. function i2met(Value,MaxLen,Pow2,Units,  Len,Div) {
  286.     if (Value == 0)
  287.     return "0"
  288.     if (!(1 in Suf))
  289.     split("K,M,G,T,P,E,Z,Y",Suf,",")
  290.     # In both awk & gawk, integer values that can be represented as
  291.     # machine integers will be printed as integers.
  292.     # If value can be printed without modification, return it as it,
  293.     # but with a multiplier suffix if reqd.
  294.     if ((Len = length(Value Suf[Units])) <= MaxLen)
  295.     return Value Suf[Units]
  296.     MaxLen -= 1        # Leave space for suffix
  297.     Div = Pow2 ? 1024 : 1000
  298.     for (Units += 1; Units in Suf; Units++)
  299.     if (length(int(Value /= Div)) <= MaxLen)
  300.         break
  301.     Value = substr(sprintf("%." MaxLen "f",Value),1,MaxLen)
  302.     if (substr(Value,MaxLen,1) == ".")
  303.     Value = substr(Value,1,MaxLen-1)
  304.     return Value Suf[Units]
  305. }
  306.  
  307. # @(#) i2emet.awk 1.0 96/02/13
  308. # jhdiii 96/01/27
  309. # Convert numeric value Value to one with the decimal point set according to
  310. # engineering convention.  In this convention, there is always between 1 and
  311. # 3 digits before the decimal point.  A metric suffix is attached to retain
  312. # the original value.
  313. # If Pow2 is true, then each factor of 1K is taken to be 1024; if it is false,
  314. # it is taken to be 1000.
  315. # If Pow2 is true, MaxLen must be >= 5; if Pow2 is false, MaxLen must be >= 4. 
  316. # If Units is given, it is the units that Value is passed in, where
  317. # Units=0 means Value is in base units; Units=-1 means Value is in milliunits,
  318. # Units=1 means Value is in kilounits, etc.
  319. # If NoZeroes is true, trailing zeroes in the fractional part are removed.
  320. function i2emet(Value,MaxLen,Pow2,Units,NoZeroes,  Len,Factor,i,suf2) {
  321.     if (Value == 0)
  322.     return "0"
  323.     if (!(1 in _Suf)) {
  324.     _MaxUnit = split("K,M,G,T,P,E,Z,Y",_Suf,",")
  325.     split("m,u,n,p,f,a,z,y",suf2,",")
  326.     for (i = 1; i in suf2; i++)
  327.         _Suf[-i] = suf2[i]
  328.     }
  329.     # Make sure awk treats all of these as numbers
  330.     Factor = (Pow2 ? 1024 : 1000)+0
  331.     Units += 0
  332.     Value += 0
  333.     if (Value < 1)
  334.     for (; Value < 1 && Units > -_MaxUnit; Value *= Factor)
  335.         Units--
  336.     else
  337.     for (; Value >= Factor && Units < _MaxUnit; Value /= Factor)
  338.         Units++
  339.     if (Units)
  340.     MaxLen -= 1        # Leave space for suffix
  341.     # Round reasonably carefully
  342.     fDig = MaxLen-length(int(Value))-1
  343.     if (fDig > 0)
  344.     Value = sprintf("%." fDig "f",Value)+0    # Turn it back into a number!
  345.     else
  346.     Value = int(Value+0.5)
  347.     # Rounding may have caused rollover of leading digit, making the result
  348.     # exceed the allowed range (e.g. 999.6 -> 1000)
  349.     if (Value >= Factor) {
  350.     Value /= Factor
  351.     Units++
  352.     }
  353.     if (substr(Value,MaxLen,1) == ".")
  354.     Value = substr(Value,1,MaxLen-1)    # Get rid of trailing "."
  355.     else
  356.     Value = substr(Value,1,MaxLen)
  357.     if (NoZeroes && Value ~ /\..*0$/)
  358.     sub(/\.?0+$/,"",Value)
  359.     return Value _Suf[Units]
  360. }
  361.  
  362. ### Start of ProcArgs library
  363. # @(#) ProcArgs 1.11 96/12/08
  364. # 92/02/29 john h. dubois iii (john@armory.com)
  365. # 93/07/18 Added "#" arg type
  366. # 93/09/26 Do not count -h against MinArgs
  367. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  368. #          Removed meaning of "+" or "-" by itself.
  369. # 94/03/08 Added & option and *()< option types.
  370. # 94/04/02 Added NoRCopt to Opts()
  371. # 94/06/11 Mark numeric variables as such.
  372. # 94/07/08 Opts(): Do not require any args if h option is given.
  373. # 95/01/22 Record options given more than once.  Record option num in argv.
  374. # 95/06/08 Added ExclusiveOptions().
  375. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  376. #          Expand $VARNAME at the start of its filenames.
  377. #          Let varname=0 and -option- turn off an option.
  378. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  379. #          of the vars should be searched for in the environment.
  380. #          Check for duplicate rcfiles.
  381. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  382. #          now return various negatives values on error, not just -1, and
  383. #          Opts() may set Err to various positive values, not just 1.
  384. #          Added AllowUnrecOpt.
  385. # 96/05/23 Check type given for & option
  386. # 96/06/15 Re-port to awk
  387. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  388. #          used by other functions.
  389. # 96/10/15 Added OptChars
  390. # 96/11/01 Added exOpts arg to Opts()
  391. # 96/11/16 Added ; type
  392. # 96/12/08 Added Opt2Set() & Opt2Sets()
  393. # 96/12/27 Added CmdLineOpt()
  394.  
  395. # optlist is a string which contains all of the possible command line options.
  396. # A character followed by certain characters indicates that the option takes
  397. # an argument, with type as follows:
  398. # :    String argument
  399. # ;    Non-empty string argument
  400. # *    Floating point argument
  401. # (    Non-negative floating point argument
  402. # )    Positive floating point argument
  403. # #    Integer argument
  404. # <    Non-negative integer argument
  405. # >    Positive integer argument
  406. # The only difference the type of argument makes is in the runtime argument
  407. # error checking that is done.
  408.  
  409. # The & option is a special case used to get numeric options without the
  410. # user having to give an option character.  It is shorthand for [-+.0-9].
  411. # If & is included in optlist and an option string that begins with one of
  412. # these characters is seen, the value given to "&" will include the first
  413. # char of the option.  & must be followed by a type character other than ":"
  414. # or ";".
  415. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  416.  
  417. # Strings in argv[] which begin with "-" or "+" are taken to be
  418. # strings of options, except that a string which consists solely of "-"
  419. # or "+" is taken to be a non-option string; like other non-option strings,
  420. # it stops the scanning of argv and is left in argv[].
  421. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  422. # If an option takes an argument, the argument may either immediately
  423. # follow it or be given separately.
  424. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  425. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  426. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  427. # this feature had a flaw that caused problems in some cases.  See the OptChars
  428. # parameter to explicitly set the option-specifier characters.
  429.  
  430. # If an option that does not take an argument is given,
  431. # an index with its name is created in Options and its value is set to the
  432. # number of times it occurs in argv[].
  433.  
  434. # If an option that does take an argument is given, an index with its name is
  435. # created in Options and its value is set to the value of the argument given
  436. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  437. # If an option that takes an argument is given more than once,
  438. # Options[option-name,"count"] is incremented, and the value is assigned to
  439. # the index (option-name,instance) where instance is 2 for the second occurance
  440. # of the option, etc.
  441. # In other words, the first time an option with a value is encountered, the
  442. # value is assigned to an index consisting only of its name; for any further
  443. # occurances of the option, the value index has an extra (count) dimension.
  444.  
  445. # The sequence number for each option found in argv[] is stored in
  446. # Options[option-name,"num",instance], where instance is 1 for the first
  447. # occurance of the option, etc.  The sequence number starts at 1 and is
  448. # incremented for each option, both those that have a value and those that
  449. # do not.  Options set from a config file have a value of 0 assigned to this.
  450.  
  451. # Options and their arguments are deleted from argv.
  452. # Note that this means that there may be gaps left in the indices of argv[].
  453. # If compress is nonzero, argv[] is packed by moving its elements so that
  454. # they have contiguous integer indices starting with 0.
  455. # Option processing will stop with the first unrecognized option, just as
  456. # though -- was given except that unlike -- the unrecognized option will not be
  457. # removed from ARGV[].  Normally, an error value is returned in this case.
  458. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  459. # be found, so the number of remaining arguments is returned instead.
  460. # If OptChars is not a null string, it is the set of characters that indicate
  461. # that an argument is an option string if the string begins with one of the
  462. # characters.  A string consisting solely of two of the same option-indicator
  463. # characters stops the scanning of argv[].  The default is "-+".
  464. # argv[0] is not examined.
  465. # The number of arguments left in argc is returned.
  466. # If an error occurs, the global string OptErr is set to an error message
  467. # and a negative value is returned.
  468. # Current error values:
  469. # -1: option that required an argument did not get it.
  470. # -2: argument of incorrect type supplied for an option.
  471. # -3: unrecognized (invalid) option.
  472. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  473. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  474. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  475. {
  476. # ArgNum is the index of the argument being processed.
  477. # ArgsLeft is the number of arguments left in argv.
  478. # Arg is the argument being processed.
  479. # ArgLen is the length of the argument being processed.
  480. # ArgInd is the position of the character in Arg being processed.
  481. # Option is the character in Arg being processed.
  482. # Pos is the position in OptList of the option being processed.
  483. # NumOpt is true if a numeric option may be given.
  484.     ArgsLeft = argc
  485.     NumOpt = index(OptList,"&")
  486.     OptionNum = 0
  487.     if (OptChars == "")
  488.     OptChars = "-+"
  489.     while (OptChars != "") {
  490.     c = substr(OptChars,1,1)
  491.     OptChars = substr(OptChars,2)
  492.     OptCharSet[c]
  493.     OptTerm[c c]
  494.     }
  495.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  496.     Arg = argv[ArgNum]
  497.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  498.         break    # Not an option; quit
  499.     if (Arg in OptTerm) {
  500.         delete argv[ArgNum]
  501.         ArgsLeft--
  502.         break
  503.     }
  504.     ArgLen = length(Arg)
  505.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  506.         Option = substr(Arg,ArgInd,1)
  507.         if (NumOpt && Option ~ /[-+.0-9]/) {
  508.         # If this option is a numeric option, make its flag be & and
  509.         # its option string flag position be the position of & in
  510.         # the option string.
  511.         Option = "&"
  512.         Pos = NumOpt
  513.         # Prefix Arg with a char so that ArgInd will point to the
  514.         # first char of the numeric option.
  515.         Arg = "&" Arg
  516.         ArgLen++
  517.         }
  518.         # Find position of flag in option string, to get its type (if any).
  519.         # Disallow & as literal flag.
  520.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  521.         if (AllowUnrecOpt) {
  522.             Escape = 1
  523.             break
  524.         }
  525.         else {
  526.             OptErr = "Invalid option: " specGiven Option
  527.             return -3
  528.         }
  529.         }
  530.  
  531.         # Find what the value of the option will be if it takes one.
  532.         # NeedNextOpt is true if the option specifier is the last char of
  533.         # this arg, which means that if the option requires a value it is
  534.         # the next arg.
  535.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  536.         if (GotValue = ArgNum + 1 < argc)
  537.             Value = argv[ArgNum+1]
  538.         }
  539.         else {    # Value is included with option
  540.         Value = substr(Arg,ArgInd + 1)
  541.         GotValue = 1
  542.         }
  543.  
  544.         if (HadValue = AssignVal(Option,Value,Options,
  545.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  546.         specGiven)) {
  547.         if (HadValue < 0)    # error occured
  548.             return HadValue
  549.         if (HadValue == 2)
  550.             ArgInd++    # Account for the single-char value we used.
  551.         else {
  552.             if (NeedNextOpt) {    # option took next arg as value
  553.             delete argv[++ArgNum]
  554.             ArgsLeft--
  555.             }
  556.             break    # This option has been used up
  557.         }
  558.         }
  559.     }
  560.     if (Escape)
  561.         break
  562.     # Do not delete arg until after processing of it, so that if it is not
  563.     # recognized it can be left in ARGV[].
  564.     delete argv[ArgNum]
  565.     ArgsLeft--
  566.     }
  567.     if (compress != 0) {
  568.     dest = 1
  569.     src = argc - ArgsLeft + 1
  570.     for (count = ArgsLeft - 1; count; count--) {
  571.         ARGV[dest] = ARGV[src]
  572.         dest++
  573.         src++
  574.     }
  575.     }
  576.     return ArgsLeft
  577. }
  578.  
  579. # Assignment to values in Options[] occurs only in this function.
  580. # Option: Option specifier character.
  581. # Value: Value to be assigned to option, if it takes a value.
  582. # Options[]: Options array to return values in.
  583. # ArgType: Argument type specifier character.
  584. # GotValue: Whether any value is available to be assigned to this option.
  585. # Name: Name of option being processed.
  586. # OptionNum: Number of this option (starting with 1) if set in argv[],
  587. #     or 0 if it was given in a config file or in the environment.
  588. # SingleOpt: true if the value (if any) that is available for this option was
  589. #     given as part of the same command line arg as the option.  Used only for
  590. #     options from the command line.
  591. # specGiven is the option specifier character use, if any (e.g. - or +),
  592. # for use in error messages.
  593. # Global variables: OptErr
  594. # Return value: negative value on error, 0 if option did not require an
  595. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  596. # the arg.
  597. # Current error values:
  598. # -1: Option that required an argument did not get it.
  599. # -2: Value of incorrect type supplied for option.
  600. # -3: Bad type given for option &
  601. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  602. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  603.     # If option takes a value...    [
  604.     NumTypes = "*()#<>]"
  605.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  606.     OptErr = "Bad type given for & option"
  607.     return -3
  608.     }
  609.  
  610.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  611.     if (!GotValue) {
  612.         if (Name != "")
  613.         OptErr = "Variable requires a value -- " Name
  614.         else
  615.         OptErr = "option requires an argument -- " Option
  616.         return -1
  617.     }
  618.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  619.         OptErr = Err
  620.         return -2
  621.     }
  622.     # Mark this as a numeric variable; will be propogated to Options[] val.
  623.     if (ArgType != ":" && ArgType != ";")
  624.         Value += 0
  625.     if ((Instance = ++Options[Option,"count"]) > 1)
  626.         Options[Option,Instance] = Value
  627.     else
  628.         Options[Option] = Value
  629.     }
  630.     # If this is an environ or rcfile assignment & it was given a value...
  631.     else if (!OptionNum && Value != "") {
  632.     UsedValue = 1
  633.     # If the value is "0" or "-" and this is the first instance of it,
  634.     # do not set Options[Option]; this allows an assignment in an rcfile to
  635.     # turn off an option (for the simple "Option in Options" test) in such
  636.     # a way that it cannot be turned on in a later file.
  637.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  638.         Instance = 1
  639.     else
  640.         Instance = ++Options[Option]
  641.     # Save the value even though this is a flag
  642.     Options[Option,Instance] = Value
  643.     }
  644.     # If this is a command line flag and has a - following it in the same arg,
  645.     # it is being turned off.
  646.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  647.     UsedValue = 2
  648.     if (Option in Options)
  649.         Instance = ++Options[Option]
  650.     else
  651.         Instance = 1
  652.     Options[Option,Instance]
  653.     }
  654.     # If this is a flag assignment without a value, increment the count for the
  655.     # flag unless it was turned off.  The indicator for a flag being turned off
  656.     # is that the flag index has not been set in Options[] but it has an
  657.     # instance count.
  658.     else if (Option in Options || !((Option,1) in Options))
  659.     # Increment number of times this flag seen; will inc null value to 1
  660.     Instance = ++Options[Option]
  661.     Options[Option,"num",Instance] = OptionNum
  662.     return UsedValue
  663. }
  664.  
  665. # Option is the option letter
  666. # Value is the value being assigned
  667. # Name is the var name of the option, if any
  668. # ArgType is one of:
  669. # :    String argument
  670. # ;    Non-null string argument
  671. # *    Floating point argument
  672. # (    Non-negative floating point argument
  673. # )    Positive floating point argument
  674. # #    Integer argument
  675. # <    Non-negative integer argument
  676. # >    Positive integer argument
  677. # specGiven is the option specifier character use, if any (e.g. - or +),
  678. # for use in error messages.
  679. # Returns null on success, err string on error
  680. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  681.     if (ArgType == ":")
  682.     return ""
  683.     if (ArgType == ";") {
  684.     if (Value == "")
  685.         Err = "must be a non-empty string"
  686.     }
  687.     # A number begins with optional + or -, and is followed by a string of
  688.     # digits or a decimal with digits before it, after it, or both
  689.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  690.     Err = "must be a number"
  691.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  692.     Err = "may not include a fraction"
  693.     else if (ArgType ~ "[()<>]" && Value < 0)
  694.     Err = "may not be negative"
  695.     # (
  696.     else if (ArgType ~ "[)>]" && Value == 0)
  697.     Err = "must be a positive number"
  698.     if (Err != "") {
  699.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  700.     if (Name != "")
  701.         return ErrStr "variable " substr(Name,1,1) " " Err
  702.     else {
  703.         if (Option == "&")
  704.         Option = Value
  705.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  706.     }
  707.     }
  708.     else
  709.     return ""
  710. }
  711.  
  712. # Note: only the above functions are needed by ProcArgs.
  713. # The rest of these functions call ProcArgs() and also do other
  714. # option-processing stuff.
  715.  
  716. # Opts: Process command line arguments.
  717. # Opts processes command line arguments using ProcArgs()
  718. # and checks for errors.  If an error occurs, a message is printed
  719. # and the program is exited.
  720. #
  721. # Input variables:
  722. # Name is the name of the program, for error messages.
  723. # Usage is a usage message, for error messages.
  724. # OptList the option description string, as used by ProcArgs().
  725. # MinArgs is the minimum number of non-option arguments that this
  726. # program should have, non including ARGV[0] and +h.
  727. # If the program does not require any non-option arguments,
  728. # MinArgs should be omitted or given as 0.
  729. # rcFiles, if given, is a colon-seprated list of filenames to read for
  730. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  731. # by the value of the environment variable HOME.  If a filename begins with
  732. # $, the part from the character after the $ up until (but not including)
  733. # the first character not in [a-zA-Z0-9_] will be searched for in the
  734. # environment; if found its value will be substituted, if not the filename will
  735. # be discarded.
  736. # rcfiles are read in the order given.
  737. # Values given in them will not override values given on the command line,
  738. # and values given in later files will not override those set in earlier
  739. # files, because AssignVal() will store each with a different instance index.
  740. # The first instance of each variable, either on the command line or in an
  741. # rcfile, will be stored with no instance index, and this is the value
  742. # normally used by programs that call this function.
  743. # VarNames is a comma-separated list of variable names to map to options,
  744. # in the same order as the options are given in OptList.
  745. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  746. # searched for in the environment.  If set to -1, all values will be searched
  747. # for in the environment.  Values given in the environment will override
  748. # those given in the rcfiles but not those given on the command line.
  749. # NoRCopt, if given, is an additional letter option that if given on the
  750. # command line prevents the rcfiles from being read.
  751. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  752. # ExclusiveOptions() for a description of exOpts.
  753. # Special options:
  754. # If x is made an option and is given, some debugging info is output.
  755. # h is assumed to be the help option.
  756.  
  757. # Global variables:
  758. # The command line arguments are taken from ARGV[].
  759. # The arguments that are option specifiers and values are removed from
  760. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  761. # The number of elements in ARGV[] should be in ARGC.
  762. # After processing, ARGC is set to the number of elements left in ARGV[].
  763. # The option values are put in Options[].
  764. # On error, Err is set to a positive integer value so it can be checked for in
  765. # an END block.
  766. # Return value: The number of elements left in ARGV is returned.
  767. # Must keep OptErr global since it may be set by InitOpts().
  768. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  769. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  770.     if (MinArgs == "")
  771.     MinArgs = 0
  772.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  773.     optChars)
  774.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  775.     if (ArgsLeft >= 0) {
  776.         OptErr = "Not enough arguments"
  777.         Err = 4
  778.     }
  779.     else
  780.         Err = -ArgsLeft
  781.     printf "%s: %s.\nUse -h for help.\n%s\n",
  782.     Name,OptErr,Usage > "/dev/stderr"
  783.     exit 1
  784.     }
  785.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  786.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  787.     {
  788.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  789.     Err = -e
  790.     exit 1
  791.     }
  792.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  793.     {
  794.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  795.     Err = 1
  796.     exit 1
  797.     }
  798.     return ArgsLeft
  799. }
  800.  
  801. # ReadConfFile(): Read a file containing var/value assignments, in the form
  802. # <variable-name><assignment-char><value>.
  803. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  804. # line and whitespace between the variable name and the assignment character) 
  805. # is stripped.  Lines that do not contain an assignment operator or which
  806. # contain a null variable name are ignored, other than possibly being noted in
  807. # the return value.  If more than one assignment is made to a variable, the
  808. # first assignment is used.
  809. # Input variables:
  810. # File is the file to read.
  811. # Comment is the line-comment character.  If it is found as the first non-
  812. #     whitespace character on a line, the line is ignored.
  813. # Assign is the assignment string.  The first instance of Assign on a line
  814. #     separates the variable name from its value.
  815. # If StripWhite is true, whitespace around the value (whitespace between the
  816. #     assignment char and trailing whitespace on the line) is stripped.
  817. # VarPat is a pattern that variable names must match.  
  818. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  819. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  820. #     a line; no assignment operator is needed.  These variables are set in
  821. #     the output array with a null value.  Lines containing nothing but
  822. #     whitespace are still ignored.
  823. # Output variables:
  824. # Values[] contains the assignments, with the indexes being the variable names
  825. #     and the values being the assigned values.
  826. # Lines[] contains the line number that each variable occured on.  A flag set
  827. #     is record by giving it an index in Lines[] but not in Values[].
  828. # Return value:
  829. # If any errors occur, a string consisting of descriptions of the errors
  830. # separated by newlines is returned.  In no case will the string start with a
  831. # numeric value.  If no errors occur,  the number of lines read is returned.
  832. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  833. FlagsOK,
  834. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  835.     if (Comment != "")
  836.     Comment = "^" Comment
  837.     AssignLen = length(Assign)
  838.     if (VarPat == "")
  839.     VarPat = "."    # null varname not allowed
  840.     while ((Status = (getline Line < File)) == 1) {
  841.     LineNum++
  842.     sub("^[ \t]+","",Line)
  843.     if (Line == "")        # blank line
  844.         continue
  845.     if (Comment != "" && Line ~ Comment)
  846.         continue
  847.     if (Pos = index(Line,Assign)) {
  848.         Var = substr(Line,1,Pos-1)
  849.         Val = substr(Line,Pos+AssignLen)
  850.         if (StripWhite) {
  851.         sub("^[ \t]+","",Val)
  852.         sub("[ \t]+$","",Val)
  853.         }
  854.     }
  855.     else {
  856.         Var = Line    # If no value, var is entire line
  857.         Val = ""
  858.     }
  859.     if (!FlagsOK && Val == "") {
  860.         Errs = Errs \
  861.         sprintf("\nBad assignment on line %d of file %s: %s",
  862.         LineNum,File,Line)
  863.         continue
  864.     }
  865.     sub("[ \t]+$","",Var)
  866.     if (Var !~ VarPat) {
  867.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  868.         LineNum,File,Var)
  869.         continue
  870.     }
  871.     if (!(Var in Lines)) {
  872.         Lines[Var] = LineNum
  873.         if (Pos)
  874.         Values[Var] = Val
  875.     }
  876.     }
  877.     if (Status)
  878.     Errs = Errs "\nCould not read file " File
  879.     close(File)
  880.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  881. }
  882.  
  883. # Variables:
  884. # Data is stored in Options[].
  885. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  886. # Global vars:
  887. # Sets OptErr.  Uses ENVIRON[].
  888. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  889. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  890. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  891. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  892.     split("",filesRead,"")    # make awk know this is an array
  893.     NumVars = split(VarNames,Vars,",")
  894.     TypesInd = Ret = 0
  895.     if (EnvSearch == -1)
  896.     EnvSearch = NumVars
  897.     for (i = 1; i <= NumVars; i++) {
  898.     Var = Vars[i]
  899.     CharOpt = substr(OptList,++TypesInd,1)
  900.     if (CharOpt ~ "^[:;*()#<>&]$")
  901.         CharOpt = substr(OptList,++TypesInd,1)
  902.     Map[Var] = CharOpt
  903.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  904.     # Do not overwrite entries from environment
  905.     if (i <= EnvSearch && Var in ENVIRON &&
  906.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  907.         return Err
  908.     }
  909.  
  910.     numrcFiles = split(rcFiles,fNames,":")
  911.     for (i = 1; i <= numrcFiles; i++) {
  912.     rcFile = fNames[i]
  913.     if (rcFile ~ "^~/")
  914.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  915.     else if (rcFile ~ /^\$/) {
  916.         rcFile = substr(rcFile,2)
  917.         match(rcFile,"^[a-zA-Z0-9_]*")
  918.         envvar = substr(rcFile,1,RLENGTH)
  919.         if (envvar in ENVIRON)
  920.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  921.         else
  922.         continue
  923.     }
  924.     if (rcFile in filesRead)
  925.         continue
  926.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  927.     # may be the same
  928.     filesRead[rcFile]
  929.     if ("x" in Options)
  930.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  931.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  932.     if (retStr > 0)
  933.         READ_RCFILE = 1
  934.     else if (ret != "") {
  935.         OptErr = retStr
  936.         Ret = -1
  937.     }
  938.     for (Var in Lines)
  939.         if (Var in Map) {
  940.         if ((Err = AssignVal(Map[Var],
  941.         Var in Values ? Values[Var] : "",Options,Types[Var],
  942.         Var in Values,Var,0)) < 0)
  943.             return Err
  944.         }
  945.         else {
  946.         OptErr = sprintf(\
  947.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  948.         Lines[Var],rcFile)
  949.         Ret = -1
  950.         }
  951.     }
  952.  
  953.     if ("x" in Options)
  954.     for (Var in Map)
  955.         if (Map[Var] in Options)
  956.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  957.         "/dev/stderr"
  958.         else
  959.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  960.     return Ret
  961. }
  962.  
  963. # OptSets is a semicolon-separated list of sets of option sets.
  964. # Within a list of option sets, the option sets are separated by commas.  For
  965. # each set of sets, if any option in one of the sets is in Options[] AND any
  966. # option in one of the other sets is in Options[], an error string is returned.
  967. # If no conflicts are found, nothing is returned.
  968. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  969. # the exclusions presented by the first set of sets (ab,def,g) if:
  970. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  971. # (a or b is in Options[]) AND (g is in Options) OR
  972. # (d, e, or f is in Options[]) AND (g is in Options)
  973. # An error will be returned due to the exclusions presented by the second set
  974. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  975. # todo: make options given on command line unset options given in config file
  976. # todo: that they conflict with.
  977. function ExclusiveOptions(OptSets,Options,
  978. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  979. SetNum,OSetNum) {
  980.     NumSetSets = split(OptSets,SetSets,";")
  981.     # For each set of sets...
  982.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  983.     # NumSets is the number of sets in this set of sets.
  984.     NumSets = split(SetSets[SetSet],Sets,",")
  985.     # For each set in a set of sets except the last...
  986.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  987.         s1 = Sets[SetNum]
  988.         L1 = length(s1)
  989.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  990.         # If any of the options in this set was given, check whether
  991.         # any of the options in the other sets was given.  Only check
  992.         # later sets since earlier sets will have already been checked
  993.         # against this set.
  994.         if ((c1 = substr(s1,Pos1,1)) in Options)
  995.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  996.             s2 = Sets[OSetNum]
  997.             L2 = length(s2)
  998.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  999.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1000.                 ErrStr = ErrStr "\n"\
  1001.                 sprintf("Cannot give both %s and %s options.",
  1002.                 c1,c2)
  1003.             }
  1004.     }
  1005.     }
  1006.     if (ErrStr != "")
  1007.     return substr(ErrStr,2)
  1008.     return ""
  1009. }
  1010.  
  1011. # The value of each instance of option Opt that occurs in Options[] is made an
  1012. # index of Set[].
  1013. # The return value is the number of instances of Opt in Options.
  1014. function Opt2Set(Options,Opt,Set,  count) {
  1015.     if (!(Opt in Options))
  1016.     return 0
  1017.     Set[Options[Opt]]
  1018.     count = Options[Opt,"count"]
  1019.     for (; count > 1; count--)
  1020.     Set[Options[Opt,count]]
  1021.     return count
  1022. }
  1023.  
  1024. # The value of each instance of option Opt that occurs in Options[] that
  1025. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1026. # Other values are made indexes of Set[].
  1027. # The return value is the number of instances of Opt in Options.
  1028. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1029.     ret = Opt2Set(Options,Opt,aSet)
  1030.     for (value in aSet)
  1031.     if (substr(value,1,1) == "!")
  1032.         nSet[substr(value,2)]
  1033.     else
  1034.         Set[value]
  1035.     return ret
  1036. }
  1037.  
  1038. # Returns true if option Opt was given on the command line.
  1039. function CmdLineOpt(Options,Opt,  i) {
  1040.     for (i = 1; (Opt,"num",i) in Options; i++)
  1041.     if (Options[Opt,"num",i] != 0)
  1042.         return 1
  1043.     return 0
  1044. }
  1045. ### End of ProcArgs library
  1046. ### Begin head-tail routines
  1047.  
  1048. # @(#) HeadTail.awk 96/05/09
  1049. # 95/04/28 Added tail routines.
  1050. # 96/05/09 Added all args to HeadTailInit()
  1051.  
  1052. # Turn on screen-bounded printing.
  1053. # Current implementation sets global vars LINES, COLUMNS, LINEGAP, and COLGAP.
  1054. # Sets the number of screen lines and rows to Lines and Rows.
  1055. # If -1 is passed for either, turns off bounding in that dimension.
  1056. # If either is not set or 0 is passed for it, its value is taken from the
  1057. # environment, or if not set there, from terminfo, or if not set there, from
  1058. # the defaults (24 and 80).
  1059. # By default, the other functions in this library leave a "grace space" of
  1060. # 1 column and 1 line.  If LineGap or ColGap is passed and is a non-negative
  1061. # value, the line gap is set to it.
  1062. function HeadTailInit(Lines,Cols,LineGap,ColGap,  Cmd) {
  1063.     # tput will use values in environment, but we want to avoid running
  1064.     # it if possible.
  1065.     if (Cols > 0)
  1066.     COLUMNS = Cols
  1067.     else if (!Cols)
  1068.     if ("COLUMNS" in ENVIRON)
  1069.         COLUMNS = ENVIRON["COLUMNS"]
  1070.     else {
  1071.         Cmd = "exec tput cols"
  1072.         Cmd | getline COLUMNS
  1073.         close(Cmd)
  1074.         if (COLUMNS == "")
  1075.         COLUMNS = 80
  1076.     }
  1077.     if (Lines > 0)
  1078.     LINES = Lines
  1079.     else if (!Lines)
  1080.     if ("LINES" in ENVIRON)
  1081.         LINES = ENVIRON["LINES"]
  1082.     else {
  1083.         Cmd = "exec tput lines"
  1084.         Cmd | getline LINES
  1085.         close(Cmd)
  1086.         if (LINES == "")
  1087.         LINES = 24
  1088.     }
  1089.     LINEGAP = (LineGap != "" && LineGap >= 0) ? LineGap : 1
  1090.     COLGAP = (ColGap != "" && ColGap >= 0) ? ColGap : 1
  1091. }
  1092.  
  1093. # Do screen-bound printing.  
  1094. # If LINES  is >0, the last LINES-LINEGAP lines are kept in a circular buffer.  
  1095. # When TailFlush() is called, they are printed.
  1096. # If LINES = 0, all lines are printed immediately.
  1097. # If COLUMNS is >0, truncates Line to COLUMNS-COLGAP characters before printing
  1098. # it.
  1099. # Global vars: uses LINES & COLUMNS; sets/uses TailPtr;
  1100. # saves lines in TailLines[] from 1..LINES-LINEGAP
  1101. # Embedded newlines split the line into multiple lines; trailing newlines are
  1102. # stripped.  Tabs are expanded to spaces.
  1103. function TailPrint(Line) {
  1104.     if (!LINES)
  1105.     print Line
  1106.     else {
  1107.     if (++TailPtr > (LINES-LINEGAP))
  1108.         TailPtr = 1
  1109.     TailLines[TailPtr] = Line
  1110.     }
  1111. }
  1112.  
  1113. function TailFlush(  NumPrinted,Lines,Line,i,Buffer,PrintLines) {
  1114.     if (!LINES)
  1115.     return
  1116.     NumPrinted = 0
  1117.     PrintLines = LINES-LINEGAP
  1118.     # Since lines may contain multiple lines, we must create a buffer to be
  1119.     # printed by reading line buffer backwards.
  1120.     # Stop when we have copied enough lines, or if we wrap around to the end
  1121.     # and find that the entire line buffer was not used.
  1122.     while (NumPrinted < PrintLines && TailPtr in TailLines) {
  1123.     # Split line into individual lines, then process them last to first
  1124.     Num = split(TailLines[TailPtr],Lines,"\n")
  1125.     for (i = Num; i >= 1; i--) {
  1126.         Line = Lines[i]
  1127.         if (i == Num && Line == "")    # discard trailing newline
  1128.         continue
  1129.         # Put this line at the front of the print buffer
  1130.         if (COLUMNS)
  1131.         Buffer = substr(TabEx(Line),1,COLUMNS - COLGAP) "\n" Buffer
  1132.         else
  1133.         Buffer = Line "\n" Buffer
  1134.         if (++NumPrinted == PrintLines)
  1135.         break
  1136.     }
  1137.     if (!--TailPtr)    # Wrap pointer if neccessary
  1138.         TailPtr = PrintLines
  1139.     }
  1140.     printf "%s",Buffer
  1141. }
  1142.  
  1143. # Do screen-bound printing.  
  1144. # If LINES >0, returns 0 when LINES-LINEGAP lines have been printed by
  1145. # HeadPrint().  Otherwise returns 1.
  1146. # If COLUMNS is >0, truncates Line to COLUMNS-COLGAP characters before printing
  1147. # it.
  1148. # Global vars: uses LINES, COLUMNS, LINEGAP, COLGAP; sets/uses LinesPrinted.
  1149. # Line should not include newlines.
  1150. function HeadPrint(Line) {
  1151.     # Check first, in case some calls of this function to not check return
  1152.     # value, and in case LINES is 1.
  1153.     if (LINES && LinesPrinted >= (LINES-LINEGAP))
  1154.     return 0
  1155.     if (COLUMNS)
  1156.     print substr(Line,1,COLUMNS - COLGAP)
  1157.     else
  1158.     print Line
  1159.     if (LINES && ++LinesPrinted >= (LINES-LINEGAP))
  1160.     return 0
  1161.     return 1
  1162. }
  1163.  
  1164. function ColPrint(Line) {
  1165.     if (COLUMNS)
  1166.     print substr(Line,1,COLUMNS - COLGAP)
  1167.     else
  1168.     print Line
  1169.     return 1
  1170. }
  1171.  
  1172. ### End head-tail routines
  1173. ### Begin qsort routines
  1174.  
  1175. # Arr[] is an array of values with arbitrary indices.
  1176. # k[] is returned with numeric indices 1..n.
  1177. # The values in k[] are the indices of Arr[],
  1178. # ordered so that if Arr[] is stepped through
  1179. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1180. # through in order of the values of its elements.
  1181. # The return value is the number of elements in the arrays (n).
  1182. function qsortArbIndByValue(Arr,k,  ArrInd,ElNum) {
  1183.     ElNum = 0
  1184.     for (ArrInd in Arr)
  1185.     k[++ElNum] = ArrInd
  1186.     qsortSegment(Arr,k,1,ElNum)
  1187.     return ElNum
  1188. }
  1189.  
  1190. # Sort a segment of an array.
  1191. # Arr[] contains data with arbitrary indices.
  1192. # k[] has indices 1..nelem, with the indices of arr[] as values.
  1193. # This function sorts the elements of arr that are pointed to by
  1194. # k[start..end], swapping the values of elements of k[] so that
  1195. # when this function returns arr[k[start..end]] will be in order.
  1196. function qsortSegment(Arr,k,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1197.     # handle two-element case explicitly for a tiny speedup
  1198.     if ((end - start) == 1) {
  1199.     if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
  1200.         k[start] = tmpe
  1201.         k[end] = tmps
  1202.     }
  1203.     return
  1204.     }
  1205.     # Make sure comparisons act on these as numbers
  1206.     left = start+0
  1207.     right = end+0
  1208.     sepval = Arr[k[int((left + right) / 2)]]
  1209.     # Make every element <= sepval be to the left of every element > sepval
  1210.     while (left < right) {
  1211.     while (Arr[k[left]] < sepval)
  1212.         left++
  1213.     while (Arr[k[right]] > sepval)
  1214.         right--
  1215.     if (left < right) {
  1216.         tmp = k[left]
  1217.         k[left++] = k[right]
  1218.         k[right--] = tmp
  1219.     }
  1220.     }
  1221.     if (left == right)
  1222.     if (Arr[k[left]] < sepval)
  1223.         left++
  1224.     else
  1225.         right--
  1226.     if (start < right)
  1227.     qsortSegment(Arr,k,start,right)
  1228.     if (left < end)
  1229.     qsortSegment(Arr,k,left,end)
  1230. }
  1231.  
  1232. # Arr[] is an array of values with arbitrary indices.
  1233. # k[] is returned with numeric indices 1..n.
  1234. # The values in k are the indices of Arr[],
  1235. # ordered so that if Arr[] is stepped through
  1236. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1237. # through in order of the values of its indices.
  1238. # The return value is the number of elements in the arrays (n).
  1239. # If the indexes are numeric, Numeric should be true, so that they can be
  1240. # compared as such rather than as strings.  Numeric indexes do not have to be
  1241. # contiguous.
  1242. function qsortByArbIndex(Arr,k,Numeric,  ArrInd,ElNum) {
  1243.     ElNum = 0
  1244.     if (Numeric)
  1245.     # Indexes do not preserve numeric type, so must be forced
  1246.     for (ArrInd in Arr)
  1247.         k[++ElNum] = ArrInd+0
  1248.     else
  1249.     for (ArrInd in Arr)
  1250.         k[++ElNum] = ArrInd
  1251.     qsortNumIndByValue(k,1,ElNum)
  1252.     return ElNum
  1253. }
  1254.  
  1255. # Arr is an array of elements with contiguous numeric indexes to be sorted
  1256. # by value.
  1257. # start and end are the starting and ending indexes of the range to be sorted.
  1258. function qsortNumIndByValue(Arr,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1259.     # handle two-element case explicitly for a tiny speedup
  1260.     if ((start - end) == 1) {
  1261.     if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
  1262.         Arr[start] = tmpe
  1263.         Arr[end] = tmps
  1264.     }
  1265.     return
  1266.     }
  1267.     left = start+0
  1268.     right = end+0
  1269.     sepval = Arr[int((left + right) / 2)]
  1270.     while (left < right) {
  1271.     while (Arr[left] < sepval)
  1272.         left++
  1273.     while (Arr[right] > sepval)
  1274.         right--
  1275.     if (left <= right) {
  1276.         tmp = Arr[left]
  1277.         Arr[left++] = Arr[right]
  1278.         Arr[right--] = tmp
  1279.     }
  1280.     }
  1281.     if (start < right)
  1282.     qsortNumIndByValue(Arr,start,right)
  1283.     if (left < end)
  1284.     qsortNumIndByValue(Arr,left,end)
  1285. }
  1286.  
  1287. ### End qsort routines
  1288.